/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 **************************************************************************
 * MIFSS  - content storage system
 * 
 *
 * @uthors: uros.kristan@gmail.com (Uroš Kristan ) Urosk.NET
 *         jernej.svigelj@gmail.com (Jernej Švigelj) 
 */
package net.monare.mifss.managers;

import net.monare.mifss.configurations.pojo.*;
import net.monare.mifss.exceptions.*;
import net.monare.mifss.lib.validators.StorageDefValidator;
import net.monare.mifss.workers.interfaces.ApiKeyHandler;
import net.monare.mifss.workers.interfaces.ConfigurationHandler;
import net.monare.mifss.workers.interfaces.ContentMetaDataDao;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;

@Component
public class StorageManager {

    private static final Logger logger = Logger.getLogger(StorageManager.class);

    @Autowired
    private ContentMetaDataDao contentMetaDataDao;

    @Autowired
    private ConfigurationHandler configurationHandler;

    @Autowired
    private PoolManager poolManager;

    @Autowired
    private ApiKeyHandler apiKeyHandler;


    public void createStorage(String storageSetName, StorageDef storageDef) throws StorageManagerException {

        try {

            // check null
            if (storageSetName == null || storageDef == null) {
                String error = "Storage Set or storage def can not be null! ";
                logger.error(error);
                throw new StorageManagerException(error);
            }

            // check storage set
            if (!configurationHandler.storageSetDefinitionExists(storageSetName)) {
                String error = "Storage Set with this name:" + storageSetName + " doesn't exists ! ";
                logger.error(error);
                throw new StorageManagerException(error);
            }

            // check if storage exists
            if (configurationHandler.storageDefinitionExists(storageDef.getName())) {
                String error = "Storage with this name:" + storageDef.getName() + " already exists ! ";
                logger.error(error);
                throw new StorageManagerException(error);
            }

            // validate storage definition
            StorageDefValidator storageDefValidator = new StorageDefValidator();
            storageDefValidator.validateStorageDefinition(storageDef);

            // storage definition is valid, create pools
            poolManager.createPools(storageDef);

            // setup max id
            contentMetaDataDao.createMaxId(storageDef);

            // id table creation procedure went OK, write new storage definition
            // to
            // configuration XML files.
            configurationHandler.addStorageDefinitionToStorageSetDefinition(storageSetName, storageDef);

            logger.info("Storage created: " + storageDef);

        } catch (ValidatorException | ConfigurationHandlerException e) {

            String errMessage = "Validation/Write operation of storage definition " + storageDef + "failed for reason: " + e.getMessage();
            logger.error(errMessage, e);

            throw new StorageManagerException(errMessage, e);

        } catch (PoolManagerException e) {

            String errMessage = "Pool creation for storage " + storageDef + "failed for reason: " + e.getMessage();
            logger.error(errMessage, e);


            throw new StorageManagerException(errMessage, e);

        } catch (ContentMetaDataDaoException e) {
            String errMessage = "Max id creation for storage " + storageDef + "failed for reason: " + e.getMessage();
            logger.error(errMessage, e);
        }

    }

    /**
     * Finds all chained storages, based on chained id
     *
     * @param parentStorageDef
     * @return
     */
    public List<StorageDef> getChildStorageDefs(StorageDef parentStorageDef) {

        List<StorageDef> childs = new ArrayList<>();

        for (StorageDef def : configurationHandler.listStorageDefinitions()) {

            if (def.isChained() && def.getChainedToStorageName().equals(parentStorageDef.getName()))

                childs.add(def);

        }
        return childs;
    }

    public StorageDef getStorageDefinition(String storageName) throws StorageManagerException {

        StorageDef storageDef = configurationHandler.getStorageDefinitionFromName(storageName);

        if (storageDef == null) {

            String errMessage = "Failed to retrieve storage definiton for given storage name:" + storageName;
            logger.error(errMessage);

            throw new StorageManagerException(errMessage);
        }

        return storageDef;
    }

    public List<String> listStorageNames() throws StorageManagerException {

        return configurationHandler.listStorageNames();

    }

    public List<StorageDef> listStorageDefinitions() {
        return configurationHandler.listStorageDefinitions();
    }

    public List<String> listAvailableTransformations() {
        return configurationHandler.listAvailableTransformations();
    }

    public List<String> listStorageSetNames() throws ConfigurationHandlerException {
        return configurationHandler.listStorageSetNames();
    }

    public List<StorageSetDef> listStorageSets() throws ConfigurationHandlerException {
        return configurationHandler.listStorageSets();
    }

    public List<StorageDef> listStorageDefsForStorageSetDef(String storageSetName) throws ConfigurationHandlerException {
        return configurationHandler.listStorageDefsForStorageSetDef(storageSetName);
    }

    public boolean setLockToStorageSetDefinition(String storageSetName, boolean locked, String lockedBy) throws ConfigurationHandlerException {
        return configurationHandler.setLockToStorageSetDefinition(storageSetName, locked, lockedBy);
    }

    public void addApiKeyToStorageDefinition(String storageDefName, ApiKey apiKey) throws ConfigurationHandlerException {
        configurationHandler.addApiKeyToStorageDefinition(storageDefName, apiKey);
    }

    public void modifyApiKey(String storageDefName, ApiKey apiKey) throws ConfigurationHandlerException {
        configurationHandler.modifyApiKey(storageDefName, apiKey);
    }

    public List<StorageDef> listStorages() throws StorageManagerException {

        return configurationHandler.listStorageDefinitions();

    }

    @PostConstruct
    public void initialize() throws ConfigurationHandlerException {
        configurationHandler.initialize();
    }

    public boolean createStorageSetDefinition(StorageSetDef newStorageSetDef) throws ConfigurationHandlerException {

        return configurationHandler.addStorageSetDefinition(newStorageSetDef);
    }

    public boolean removeStorageSetDefinition(String storageSetName) throws ConfigurationHandlerException {
        return configurationHandler.removeStorageSetDefinition(storageSetName);
    }

    public void removeStorage(String storageName, boolean removeContent) throws StorageManagerException {

        logger.info("trying to remove storage: " + storageName + " with remove content opiton is: " + removeContent);

        StorageDef storageDef = getStorageDefinition(storageName);

        //
        // Remove pools if
        if (removeContent) {

            for (ContentPoolDef contentPoolDef : storageDef.getContentPoolDefs()) {

                try {

                    poolManager.wipePoolData(contentPoolDef);
                    logger.info("pool wipped: " + contentPoolDef);

                } catch (PoolManagerException e) {

                    String errMessage = "Error occured wile trying to remove pool: " + contentPoolDef.getTitle();
                    logger.error(errMessage, e);
                    throw new StorageManagerException(errMessage);

                }
            }

        }

        //
        // Remove storage definition from configuration and remove meta data

        try {

            configurationHandler.removeStorageDefinition(storageDef.getStorageSetDef().getName(), storageDef.getName());

            logger.info("Removed storage defintion! " + storageName);

            contentMetaDataDao.removeAllContentMetaData(storageDef);

            logger.info("removed all meta data for storage: " + storageName);

            contentMetaDataDao.removeMaxId(storageDef);

            logger.info("removed max id for storage: " + storageName);

        } catch (ConfigurationHandlerException e) {

            String errMessage = "Error occured wile trying to remove storage definition " + storageDef.getTitle() + "from configuration.";
            logger.error(errMessage, e);
            throw new StorageManagerException(errMessage);

        } catch (ContentMetaDataDaoException e) {

            String errMessage = "Error occurred wile trying to clear content meta data for table: " + storageDef.getName();
            logger.error(errMessage, e);
            throw new StorageManagerException(errMessage);
        }

    }

    public ApiKey createAndWriteNewApiKeyForStorageId(String storageName, String name, String description, ApiMode apiMode) throws StorageManagerException {

        ApiKey apiKey;
        try {

            apiKey = apiKeyHandler.generateNewApiKey(name, description, apiMode);
            configurationHandler.addApiKeyToStorageDefinition(storageName, apiKey);

            return apiKey;

        } catch (ApiKeyHandlerException e) {

            String errMessage = "Error occured wile creating new api for storage  " + storageName + "from configuration.";
            logger.error(errMessage, e);
            throw new StorageManagerException(errMessage);

        } catch (ConfigurationHandlerException e) {

            String errMessage = "Error occured wile trying to add new api key to storage " + storageName + "from configuration.";
            logger.error(errMessage, e);
            throw new StorageManagerException(errMessage);

        }

    }

    public void removeApiKey(String storageName, ApiKey apiKey) throws StorageManagerException {

        try {

            configurationHandler.removeApiKey(storageName, apiKey);
        } catch (ConfigurationHandlerException e) {
            String errMessage = "Error wile trying to remove api key from storage " + storageName + "from configuration.";
            logger.error(errMessage, e);
            throw new StorageManagerException(errMessage);

        }

    }

    public boolean storageDefinitionExists(String storageName) {
        return configurationHandler.storageDefinitionExists(storageName);
    }

    public void modifyStorageDescription(String storageDefName, String description) throws StorageManagerException {

        StorageDef storageDef = configurationHandler.getStorageDefinitionFromName(storageDefName);
        storageDef.setDescription(description);

        configurationHandler.modifyStorageDefinition(storageDef.getStorageSetDef().getName(), storageDef);
    }

    public void modifyStorageTitle(String storageDefName, String title) throws StorageManagerException {

        StorageDef storageDef = configurationHandler.getStorageDefinitionFromName(storageDefName);
        storageDef.setTitle(title);

        configurationHandler.modifyStorageDefinition(storageDef.getStorageSetDef().getName(), storageDef);
    }

    public void modifyStorageMode(String storageDefName, StorageMode storageMode) throws StorageManagerException {

        StorageDef storageDef = configurationHandler.getStorageDefinitionFromName(storageDefName);
        storageDef.setMode(storageMode);

        configurationHandler.modifyStorageDefinition(storageDef.getStorageSetDef().getName(), storageDef);
    }

    public void modifyStorageTransformation(String storageDefName, String transformation) throws StorageManagerException {

        StorageDef storageDef = configurationHandler.getStorageDefinitionFromName(storageDefName);
        storageDef.setTransformationHandlerName(transformation);

        configurationHandler.modifyStorageDefinition(storageDef.getStorageSetDef().getName(), storageDef);

    }

    public void modifyStorageProperty(String storageDefName, Property property) throws StorageManagerException {

        StorageDef storageDef = configurationHandler.getStorageDefinitionFromName(storageDefName);

        storageDef.getProperties().remove(property);
        storageDef.getProperties().add(property);

        //configurationHandler.

    }

    public void addStorageProperty(String storageDefName, Property property) throws StorageManagerException {

        StorageDef storageDef = configurationHandler.getStorageDefinitionFromName(storageDefName);
    }

    public void createContentPool(String storageDefName, ContentPoolDef contentPoolDef) throws StorageManagerException {

        StorageDef storageDef = configurationHandler.getStorageDefinitionFromName(storageDefName);

        configurationHandler.addContentPoolDefinition(storageDefName, contentPoolDef);
    }

    public void modifyContentPool(String storageDefName, ContentPoolDef contentPoolDef) throws StorageManagerException {

        StorageDef storageDef = configurationHandler.getStorageDefinitionFromName(storageDefName);
    }

    public void removeContentPool(String storageDefName, ContentPoolDef contentPoolDef) throws StorageManagerException {

        StorageDef storageDef = configurationHandler.getStorageDefinitionFromName(storageDefName);
    }

    public StorageSetDef getStorageSetDefinition(String storageSetName) {
        return configurationHandler.getStorageSetDefinitionFromName(storageSetName);
    }

    public ApiKey verifyApiKey(String storageName, String apiKeyHash, ApiMode apiMode) throws StorageManagerException {

        StorageDef storageDef = configurationHandler.getStorageDefinitionFromName(storageName);

        try {

            ApiKey apiKey = apiKeyHandler.verifyApiKey(storageDef.getApiKeys(), apiKeyHash, apiMode);

            return apiKey;

        } catch (ApiKeyHandlerException e) {
            String errMessage = "Error wile trying to validate api key from storage " + storageName + ".";
            logger.error(errMessage, e);
            throw new StorageManagerException(errMessage);

        }

    }

    public List<String> listAvailablePoolImplementations() {

     return    configurationHandler.listAvailablePoolImplmentations();

    }

    public void removeStorageProperty(String storageDefName, String propertyName) throws StorageManagerException{

        StorageDef storageDef = configurationHandler.getStorageDefinitionFromName(storageDefName);
        storageDef.removeProperty(propertyName);

    }
}
